package test.springtheorem;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;

/*
*  原理简述:
*       1. 将xml里面的bean全部提取转为一张Map, key为BeanId要与被注入的字段名一致, value为实例;
*
*       2. 这张Map里面的对象其实既用来被注入, 也用来注入;
*
*       3. 遍历Map里面所有实例的所有方法, 看是否存在Resource注解, 没有就直接跳过;
*       有则看注解的name是否为空, 非空则用name来定位被注入的字段, 空则用beanId来定位, 然后直接invoke下setter;
*
*       4. 遍历Map里面所有实例的所有属性, 看是否存在Resource注解, 没有就直接跳过;
*       有则看注解的name是否为空, 非空则用name来定位被注入的字段, 空则用beanId来定位, 然后直接set(bean, value);
*
* */


/**
 * @Description: spring中的注解原理
 * @ClassName: ClassPathXMLApplicationContext
 */
public class ClassPathXMLApplicationContext {

    List<BeanDefine> beanList = new ArrayList<BeanDefine>();
    //Key:BeanId Value:实例, 注意里面包括了注入和被注入的Bean
    Map<String, Object> sigletions = new HashMap<String, Object>();

    public ClassPathXMLApplicationContext(String fileName) {
        //读取配置文件中管理的bean
        this.readXML(fileName);
        //实例化bean  
        this.instancesBean();
        //注解处理器  
        this.annotationInject();
    }

    /**
     * 读取Bean配置文件
     *
     * @param fileName
     * @return void
     */
    @SuppressWarnings("unchecked")
    public void readXML(String fileName) {
        Document document = null;
        SAXReader saxReader = new SAXReader();
        try {
            ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
            document = saxReader.read(classLoader.getResourceAsStream(fileName));
            Element beans = document.getRootElement();
            for (Iterator<Element> beansList = beans.elementIterator();
                 beansList.hasNext(); ) {
                Element element = beansList.next();
                BeanDefine bean = new BeanDefine(
                        element.attributeValue("id"),
                        element.attributeValue("class"));
                beanList.add(bean);
            }
        } catch (DocumentException e) {
            System.out.println("读取配置文件出错....");
        }
    }

    /**
     * 实例化beanList里面所有的Bean对应的class;
     * 并将beanId与实例作为K-V放入sigletions;
     * beanId与@Resource的name属性起到同样的作用, 注解没写name则用beanId来识别被注入的字段;
     */
    public void instancesBean() {
        for (BeanDefine bean : beanList) {
            try {
                sigletions.put(bean.getId(), Class.forName(bean.getClassName()).newInstance());
            } catch (Exception e) {
                System.out.println("实例化Bean出错...");
            }
        }
    }

    /**
     * 注解处理器
     * 如果注解Resource配置了name属性，则根据name所指定的名称获取要注入的实例引用，
     * 如果注解Resource;没有配置name属性，则根据属性所属类型来扫描配置文件获取要
     * 注入的实例引用
     */
    public void annotationInject() {
        // 遍历所有实例
        for (String beanName : sigletions.keySet()) {
            Object bean = sigletions.get(beanName);
            if (bean != null) {
                this.propertyAnnotation(bean);
                this.fieldAnnotation(bean);
            }
        }
    }

    /**
     * 处理在set方法加入的注解
     *
     * @param bean 处理的bean
     */
    public void propertyAnnotation(Object bean) {
        try {
            //获取其属性的描述  
            PropertyDescriptor[] ps = Introspector.getBeanInfo(bean.getClass()).getPropertyDescriptors();
            for (PropertyDescriptor proderdesc : ps) {
                //获取所有set方法  
                Method setter = proderdesc.getWriteMethod();
                //判断set方法是否定义了注解  
                if (setter != null && setter.isAnnotationPresent(Resource.class)) {
                    //获取当前注解，并判断name属性是否为空  
                    Resource resource = setter.getAnnotation(Resource.class);
                    String name = "";
                    Object value = null;
                    // 如果注解name为空则去xml里面取beanId, 不为空则直接用name里面来定位注入的字段
                    if (resource.name() != null && !"".equals(resource.name())) {
                        //获取注解的name属性的内容  
                        name = resource.name();
                        value = sigletions.get(name);
                    } else { //如果当前注解没有指定name属性,则根据类型进行匹配
                        for (String key : sigletions.keySet()) {
                            //判断当前属性所属的类型是否在配置文件中存在  
                            if (proderdesc.getPropertyType().isAssignableFrom(sigletions.get(key).getClass())) {
                                //获取类型匹配的实例对象  
                                value = sigletions.get(key);
                                break;
                            }
                        }
                    }
                    //允许访问private方法  
                    setter.setAccessible(true);
                    //把引用对象注入属性  
                    setter.invoke(bean, value);
                }
            }
        } catch (Exception e) {
            System.out.println("set方法注解解析异常..........");
        }
    }

    /**
     * 处理在字段上的注解
     *
     * @param bean 处理的bean
     */
    public void fieldAnnotation(Object bean) {
        try {
            //获取其全部的字段描述  
            Field[] fields = bean.getClass().getFields();
            for (Field f : fields) {
                // 只处理被Resource注解的字段
                if (f != null && f.isAnnotationPresent(Resource.class)) {
                    Resource resource = f.getAnnotation(Resource.class);
                    String name = "";
                    Object value = null;
                    if (resource.name() != null && !"".equals(resource.name())) {
                        name = resource.name();
                        value = sigletions.get(name);
                    } else {
                        for (String key : sigletions.keySet()) {
                            //判断当前属性所属的类型是否在配置文件中存在  
                            if (f.getType().isAssignableFrom(sigletions.get(key).getClass())) {
                                //获取类型匹配的实例对象  
                                value = sigletions.get(key);
                                break;
                            }
                        }
                    }
                    //允许访问private字段  
                    f.setAccessible(true);
                    //把引用对象注入属性  
                    f.set(bean, value); //bean为UserServiceImpl实例, value为UserDaoXImpl实例
                }
            }
        } catch (Exception e) {
            System.out.println("字段注解解析异常..........");
        }
    }

    /**
     * 获取Map中的对应的bean实例
     *
     * @param beanId
     * @return
     */
    public Object getBean(String beanId) {
        return sigletions.get(beanId);
    }


    public static void main(String[] args) {
        ClassPathXMLApplicationContext path = new ClassPathXMLApplicationContext("test/springtheorem/zzconfig.xml");
        UserServiceImpl userService = (UserServiceImpl) path.getBean("userService");
        userService.show();
    }
}  